<?php

namespace maotool\match;

use maotool\util\PathUtil;

/**
 * Ant 风格的目录扫描风格匹配
 */
class AntPathMatcher
{

    /**
     * 是否需要模式匹配
     * @param $path
     * @return bool
     */
    public function isPattern($path)
    {
        $path = PathUtil::path($path);
        return strstr($path, '*') !== false or strstr($path, '?') !== false or DIRECTORY_SEPARATOR == substr($path, '-1');
    }

    /**
     * 模式匹配
     * @param $pattern
     * @param $path
     * @return bool
     */
    public function match($pattern, $path)
    {
        $pattern = PathUtil::path($pattern);
        $path = PathUtil::path($path);
        return $this->doMatch($pattern, $path, true);
    }

    /**
     * 模式匹配 全值匹配
     * @param $pattern
     * @param $path
     * @return bool
     */
    public function matchStart($pattern, $path)
    {
        $pattern = PathUtil::path($pattern);
        $path = PathUtil::path($path);
        return $this->doMatch($pattern, $path, false);
    }

    /**
     * Actually match the given <code>path</code> against the given <code>pattern</code>.
     * @param string $pattern
     * @param string $path
     * @param boolean $fullMatch
     * @return boolean
     */
    protected function doMatch($pattern, $path, $fullMatch)
    {

        if (($pattern[0] == DIRECTORY_SEPARATOR) != ($path[0] == DIRECTORY_SEPARATOR)) {
            return false;
        }
        if (DIRECTORY_SEPARATOR == substr($pattern, '-1')) {
            $pattern .= '**';
        }

        $patternDirs = $this->tokenizeToStringArray($pattern);
        $pathDirs = $this->tokenizeToStringArray($path);

        $patternIdxStart = 0;
        $patternIdxEnd = count($patternDirs) - 1;
        $pathIdxStart = 0;
        $pathIdxEnd = count($pathDirs) - 1;

        while ($patternIdxStart <= $patternIdxEnd and $pathIdxStart <= $pathIdxEnd) {
            $patDir = $patternDirs[$patternIdxStart];
            if ('**' == $patDir) {
                break;
            }
            if (!$this->matchStrings($patDir, $pathDirs[$pathIdxStart])) {
                return false;
            }
            $patternIdxStart++;
            $pathIdxStart++;
        }

        if ($pathIdxStart > $pathIdxEnd) {
            if ($patternIdxStart > $patternIdxEnd) {
                return (DIRECTORY_SEPARATOR == substr($pattern, '-1') ? DIRECTORY_SEPARATOR == substr($path, '-1') :
                    !(DIRECTORY_SEPARATOR == substr($path, '-1')));
            }
            if (!$fullMatch) {
                return true;
            }
            if ($patternIdxStart == $patternIdxEnd and '*' == $patternDirs[$patternIdxStart] and DIRECTORY_SEPARATOR == substr($path, -1)) {
                return true;
            }
            for ($i = $patternIdxStart; $i <= $patternIdxEnd; $i++) {
                if (!'**' == $patternDirs[$i]) {
                    return false;
                }
            }

            return false;
        } elseif ($patternIdxStart > $patternIdxEnd) {
            return false;
        } elseif (!$fullMatch and "**" == $patternDirs[$patternIdxStart]) {
            return true;
        }

        while ($patternIdxStart <= $patternIdxEnd and $pathIdxStart <= $pathIdxEnd) {
            $patDir = $patternDirs[$patternIdxEnd];
            if ('**' == $patDir) {
                break;
            }
            if (!$this->matchStrings($patDir, $pathDirs[$pathIdxEnd])) {
                return false;
            }
            $patternIdxEnd--;
            $pathIdxEnd--;
        }
        if ($pathIdxStart > $pathIdxEnd) {
            for ($i = $patternIdxStart; $i <= $patternIdxEnd; $i++) {
                if (!('**' == $patternDirs[$i])) {
                    return false;
                }
            }

            return true;
        }

        while ($patternIdxStart != $patternIdxEnd and $pathIdxStart <= $pathIdxEnd) {
            $patIdxTmp = -1;
            for ($i = $patternIdxStart + 1; $i <= $patternIdxEnd; $i++) {
                if ('**' == $patternDirs[$i]) {
                    $patIdxTmp = $i;
                    break;
                }
            }
            if ($patIdxTmp == $patternIdxStart + 1) {
                $patternIdxStart++;
                continue;
            }

            $patLength = ($patIdxTmp - $patternIdxStart - 1);
            $strLength = ($pathIdxEnd - $pathIdxStart + 1);
            $foundIdx = -1;

            for ($i = 0; $i <= $strLength - $patLength; $i++) {
                for ($j = 0; $j < $patLength; $j++) {
                    $subPat = $patternDirs[$patternIdxStart + $j + 1];
                    $subStr = $pathDirs[$pathIdxStart + $i + $j];
                    if (!$this->matchStrings($subPat, $subStr)) {
                        continue 2;
                    }
                }
                $foundIdx = $pathIdxStart + $i;
                break;
            }

            if ($foundIdx == -1) {
                return false;
            }

            $patternIdxStart = $patIdxTmp;
            $pathIdxStart = $foundIdx + $patLength;

        }

        for ($i = $patternIdxStart; $i <= $patternIdxEnd; $i++) {
            if (!'**' == $patternDirs[$i]) {
                return false;
            }
        }

        return true;

    }

    protected function tokenizeToStringArray($str, $delimiter = DIRECTORY_SEPARATOR, $trimTokens = true, $ignoreEmptyTokens = true)
    {
        if ($str === null) {
            return null;
        }
        $tokens = array();
        foreach (explode($delimiter, $str) as $token) {
            if ($trimTokens) {
                $token = trim($token);
            }
            if (!$ignoreEmptyTokens or strlen($token) > 0) {
                $tokens[] = $token;
            }
        }

        return $tokens;
    }

    private function matchStringsCallback($matches)
    {
        switch ($matches[0]) {
            case '?':
                return '.';
                break;
            case '*':
                return '.*';
                break;
            case '.':
            case '+':
                return '\\' . $matches[0];
                break;
        }
        throw new \InvalidArgumentException('Path contains invalid character: ' . $matches[0]);
    }

    protected function matchStrings($pattern, $str)
    {
        $re = preg_replace_callback('([\?\*\.\+])', array($this, 'matchStringsCallback'), $pattern);

        return preg_match('/^' . $re . '$/', $str);
    }

}